package k8s

import (
	"context"
	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

type Service interface {
	GetService(namespace, name string) (*corev1.Service, error)
	CreateIfNotExistsService(namespace string, service *corev1.Service) error
	CreateService(namespace string, service *corev1.Service) error
	UpdateService(namespace string, service *corev1.Service) error
	CreateOrUpdateService(namespace string, service *corev1.Service) error
}

// ServiceService is the service service implementation using API calls to kubernetes.
type ServiceService struct {
	kubeClient kubernetes.Interface
	logger     logr.Logger
}

func newServiceService(kubeClient kubernetes.Interface, logger logr.Logger) *ServiceService {
	logger = logger.WithValues("service", "k8s.service")
	return &ServiceService{
		kubeClient: kubeClient,
		logger:     logger,
	}
}

func (s *ServiceService) GetService(namespace, name string) (*corev1.Service, error) {
	service, err := s.kubeClient.CoreV1().Services(namespace).Get(context.TODO(), name, metav1.GetOptions{})
	if err != nil {
		return nil, err
	}
	return service, err
}

func (s *ServiceService) CreateIfNotExistsService(namespace string, service *corev1.Service) error {
	if _, err := s.GetService(namespace, service.Name); err != nil {
		// if resource not exist, need create
		if errors.IsNotFound(err) {
			return s.CreateService(namespace, service)
		}
		return err
	}
	return nil
}

func (s *ServiceService) CreateService(namespace string, service *corev1.Service) error {
	_, err := s.kubeClient.CoreV1().Services(namespace).Create(context.TODO(), service, metav1.CreateOptions{})
	if err != nil {
		return err
	}
	s.logger.WithValues("namespace", namespace).WithValues("service", service.Name).Info("service created")
	return nil
}

func (s *ServiceService) UpdateService(namespace string, service *corev1.Service) error {
	_, err := s.kubeClient.CoreV1().Services(namespace).Update(context.TODO(), service, metav1.UpdateOptions{})
	if err != nil {
		return err
	}
	s.logger.WithValues("namespace", namespace).WithValues("service", service.Name).Info("service updated")
	return nil
}

func (s *ServiceService) CreateOrUpdateService(namespace string, service *corev1.Service) error {
	storedService, err := s.GetService(namespace, service.Name)
	if err != nil {
		if errors.IsNotFound(err) {
			return s.CreateService(namespace, service)
		}
		return err
	}

	// if service exist, update
	service.ResourceVersion = storedService.ResourceVersion
	service.Spec.ClusterIP = storedService.Spec.ClusterIP
	return s.UpdateService(namespace, service)
}
